[Amazon SageMaker] 30種類の商品を分類するモデルで誤検知ゼロを目指してみました
1 はじめに
CX事業本部の平内(SIN)です。
Amazon SageMaker(以下、SageMaker)のイメージ分類(組み込みアルゴリズム)で30種類の商品を分類するモデルを作成しました。
イメージ分類で、ある程度の精度を出すのは、比較的簡単ですが、誤りの少ない、精度の高いものとなると、それなりに苦労します。
今回は、贅沢にも誤検知ゼロを目指してみました。
下記は、最終的に出来上がったモデルで判定している様子です。30種類の商品を間違わずに判定できている様子を確認できます。(注:状況によっては、誤判定が発生する事もあると思います)
2 データ作成
(1) 動画撮影
元となるデータは、商品を回転台に乗せ、照明を変化させながら3回転ほどWebカメラで撮影しています。30種類の撮影時間は、小1時間程度です。
動画は、800*600 24fpsで撮影されており、各データのサイズは、20MByte程度になっています。
(2) 解像度の変換
動画は、解像度を320*240に変換しました。
変換した理由は、以前、色々確認してみたところ、モデルの入力である、224 * 224に近いほうが、精度が上がる結果となった為です。
参考:[Amazon SageMaker] イメージ分類(Image Classification)において、データセットの解像度が、学習及び、検出結果に与える影響について確認してみました
変換後のサイズは、1.5MByte程度になりました。
import os import glob import subprocess # 設定 WIDTH = 320 HEIGHT = 240 INPUT_PATH = "/tmp/" OUTPUT_PATH = "/tmp/{}-{}/".format(WIDTH, HEIGHT) # 出力先ディレクトリ作成 os.makedirs(OUTPUT_PATH, exist_ok=True) # 入力パスにある*.mp4を列挙 files = glob.glob("{}/*.mp4".format(INPUT_PATH)) for src in files: # ffmpegで解像度変換 subprocess.call('ffmpeg -i "{}" -vf scale={}:{} "{}{}"'.format(src, WIDTH, HEIGHT, OUTPUT_PATH, os.path.basename(src)), shell=True)
(3) 解像度の変更
動画は、200枚づつ切り出し、Ground Truth形式のデータセットとし、その後、イメージ形式に変換しています。
参考:[Amazon SageMaker] Amazon SageMaker Ground Truth で作成したデータをイメージ分類で利用可能なイメージ形式に変換してみました
各200件で、30種類なので、全部で6,000件となり、8:2で分割して、学習用は、4,800件となりました。
全データ: 6000件 PORIPPY(GREEN) (200件) => 160:40 OREO (200件) => 160:40 CUNTRY_MAM (200件) => 160:40 PORIPPY(RED) (200件) => 160:40 CHEDDER_CHEESE (200件) => 160:40 PRETZEL(YELLOW) (200件) => 160:40 UMIAJISEN (200件) => 160:40 KAKINOTANE(NORMAL) (200件) => 160:40 FURUGURA(BROWN) (200件) => 160:40 NOIR (200件) => 160:40 BANANA(BLOWN) (200件) => 160:40 CHEESE_ARARE (200件) => 160:40 ORENO_OYATU (200件) => 160:40 PRIME (200件) => 160:40 CRATZ(RED) (200件) => 160:40 CRATZ(GREEN) (200件) => 160:40 PORIPPY(YELLOW) (200件) => 160:40 KOTUBUKKO (200件) => 160:40 ASPARAGUS (200件) => 160:40 NORI_PI (200件) => 160:40 KAKINOTANE(UME) (200件) => 160:40 PRETZEL(BLACK) (200件) => 160:40 CRATZ(ORANGE) (200件) => 160:40 CHOCO_MERIZE (200件) => 160:40 POTATO(RED) (200件) => 160:40 BANANA(BLUE) (200件) => 160:40 DENROKU (200件) => 160:40 FURUGURA(RED) (200件) => 160:40 PRETZEL(GREEN) (200件) => 160:40 POTATO(BLUE) (200件) => 160:40 train:4800 validation:1200
3 学習
設定したパラメータは、以下のとおりです。
early_stopping false epochs 6 image_shape 3,224,224 learning_rate 0.001 mini_batch_size 32 multi_label 0 num_classes 30 num_layers 152 num_training_samples 4800 optimizer sgd precision_dtype float32 use_pretrained_model 1 use_weighted_loss 0
学習の経過です。
epoch Train-accuracy Validation-accuracy ----------------------------------------- 0 0.233 0.123 1 0.394 0.384 2 0.758 0.607 3 0.924 0.804 4 0.98 0.919 5 0.996 0.917
4 結果
実は、この6000件のデータセットで作成したモデルは、30件中6件の誤検知がありました。間違えていたのは、以下の6種類です。
- BANANA(BLOWN)が、、BANANA(BLUE)と判定されている
- CRATZ(RED)が、CRATZ(ORANGE)と判定されている
- ポリッピー(YELLOW)が、ポリッピー(RED)と判定されている
- PRETZEL(YELLOW)が、PRETZEL(GREEN)と判定されている
- ポリッピー(GREEN)が、海鮮味と判定されている
- フライドポテト(じゃがバター味)が、フライドポテト(しお味)と判定されている
言語化すると「(色違い的な)よく似た商品に誤って判定されてしまっている」と言えそうです。 そして、以下の商品が、ちょっと 「強すぎる」 って感じです。
- BANANA(BLUE)
- CRATZ(ORANGE)
- ポリッピー(RED)
- PRETZEL(GREEN)
- 海鮮味
- フライドポテト(しお味)
5 改善
当初、すべての商品を200件としましたが、改善のために、上記の 「強すぎる」 商品のデータ数を180件に削減しました。
NUMS={"PORIPPY(GREEN)":200,"OREO":200,"CUNTRY_MAM":200,"PORIPPY(RED)":180,"CHEDDER_CHEESE":200,"PRETZEL(YELLOW)":200,"UMIAJISEN":180,"KAKINOTANE(NORMAL)":200,"FURUGURA(BROWN)":200,"NOIR":200,"BANANA(BLOWN)":200,"CHEESE_ARARE":200,"ORENO_OYATU":200,"PRIME":200,"CRATZ(RED)":200,"CRATZ(GREEN)":200,"PORIPPY(YELLOW)":200,"KOTUBUKKO":200,"ASPARAGUS":200,"NORI_PI":200,"KAKINOTANE(UME)":200,"PRETZEL(BLACK)":200,"CRATZ(ORANGE)":180,"CHOCO_MERIZE":200,"POTATO(RED)":200,"BANANA(BLUE)":180,"DENROKU":200,"FURUGURA(RED)":200,"PRETZEL(GREEN)":180,"POTATO(BLUE)":180}
削減したデータをイメージ形式に変換すると、学習データは、4704件となります。
全データ: 5880件 PORIPPY(RED) (180件) => 144:36 UMIAJISEN (180件) => 144:36 CRATZ(ORANGE) (180件) => 144:36 BANANA(BLUE) (180件) => 144:36 PRETZEL(GREEN) (180件) => 144:36 POTATO(BLUE) (180件) => 144:36 PORIPPY(GREEN) (200件) => 160:40 OREO (200件) => 160:40 CUNTRY_MAM (200件) => 160:40 CHEDDER_CHEESE (200件) => 160:40 PRETZEL(YELLOW) (200件) => 160:40 KAKINOTANE(NORMAL) (200件) => 160:40 FURUGURA(BROWN) (200件) => 160:40 NOIR (200件) => 160:40 BANANA(BLOWN) (200件) => 160:40 CHEESE_ARARE (200件) => 160:40 ORENO_OYATU (200件) => 160:40 PRIME (200件) => 160:40 CRATZ(RED) (200件) => 160:40 CRATZ(GREEN) (200件) => 160:40 PORIPPY(YELLOW) (200件) => 160:40 KOTUBUKKO (200件) => 160:40 ASPARAGUS (200件) => 160:40 NORI_PI (200件) => 160:40 KAKINOTANE(UME) (200件) => 160:40 PRETZEL(BLACK) (200件) => 160:40 CHOCO_MERIZE (200件) => 160:40 POTATO(RED) (200件) => 160:40 DENROKU (200件) => 160:40 FURUGURA(RED) (200件) => 160:40 train:4704 validation:1176
このデータセットを、下記のパラメータで学習すると、最初に紹介した動画のような、30勝0敗のモデルを得ることができました。
early_stopping false epochs 7 image_shape 3,224,224 learning_rate 0.001 mini_batch_size 32 multi_label 0 num_classes 30 num_layers 152 num_training_samples 4704 optimizer sgd precision_dtype float32 use_pretrained_model 1 use_weighted_loss 0
epoch Train-accuracy Validation-accuracy ----------------------------------------- 0 0.9 0.835 1 0.945 0.937 2 0.983 0.985 3 0.991 0.957 4 0.997 0.963 5 0.996 0.964 6 1.0 0.985
6 最後に
実は、改善のための最初のアプローチは、増加学習でした。最初のモデルを引き継いで、誤っていた商品のみのデータセットで増加学習をしました。
しかし、追加した商品は、完全に検出できるようになるのですが、元々、正確に検出出来ていた商品も、追加した商品に引きずられて間違ってしまう結果となりました。(データ数を非常に少数にしても同じでした)
そして、最終的に、うまく言ったのは、今回紹介した、強くなっている商品のデータを削減するアプローチでした。
この方法が、万能であると言う訳では決してありませんが、一つの結果として紹介できれば幸いです。